package bittwiddled;

import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.Callable;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;

import static bittwiddled.BankerOrder.INTEGER.moreThan;

public class DesignabilityTools {

	private DesignabilityTools() {}
	
	public class VertexDesignability implements Callable<Integer> {
		
		private final BeforeStates b;
		
		private final int enumeratedInhibitors;
		private final int enumeratedActivators;
		private final int multiplicativeInhibitors;
		private final int multiplicativeActivators;

		VertexDesignability(BeforeStates processed, int size) {
			this.b = processed;
			enumeratedInhibitors = enumeratedInhibitors(b);
			enumeratedActivators = enumeratedActivators(b);
			multiplicativeInhibitors = multiplicativeInhibitors(b,enumeratedInhibitors,size);
			multiplicativeActivators = multiplicativeActivators(b,enumeratedActivators,size);
		}
		
		private int enumeratedDesignability;
		public int enumeratedDesignability() {
			if (minimalEnumeratedSolutions == null) {
				Iterator<EnumSolution> eIter = 
					enumerateSolutions(enumeratedActivators, enumeratedInhibitors, satisfyingActivators(b), satisfyingMixed(b)).iterator();
				EnumSolution reference = eIter.next();
				Set<EnumSolution> hold = new HashSet<EnumSolution>();
				hold.add(reference);
				int D = 1;
				while (eIter.hasNext()) {
					EnumSolution temp = eIter.next();
					if (reference.compareTo(temp) == 0) {
						hold.add(temp);
					} else if (reference.compareTo(temp) < 0) {
						hold.clear();
						reference = temp;
						hold.add(reference);
					}
					D++;
				}
				enumeratedDesignability = D;
			}
			return enumeratedDesignability;
		}
		
		public Integer call() { return enumeratedDesignability(); }
		
		private Set<Integer> minimalEnumeratedSolutions;
		public Set<Integer> minimalEnumeratedSolutions() {
			if (minimalEnumeratedSolutions == null) enumeratedDesignability();
			return minimalEnumeratedSolutions;
		}
		
		public int pow2() {
			return Integer.bitCount(multiplicativeInhibitors ^ multiplicativeActivators);
		}
		
		public int pow3() {
			return Integer.bitCount(multiplicativeInhibitors & multiplicativeActivators);
		}
	}
	
	public static final int multiplicativeInhibitors(BeforeStates b, int enumeratedInhibitors, int size) {
		return moreThan(size-1) & ~(b.inhibitors | b.nonInhibitors | enumeratedInhibitors);
	}
	
	public static final int multiplicativeActivators(BeforeStates b, int enumeratedActivators, int size) {
		return moreThan(size-1) & ~(b.activators | b.nonActivators | enumeratedActivators);
	}
	
	public static final int enumeratedInhibitors(BeforeStates b) {
		int enumeratedInhibitors = 0;
		
		for (int rterm : b.requiredBeforeOFF()) enumeratedInhibitors |= rterm;
		for (Entry<Integer,Integer> mixedTerm : b.beforeOFF().entrySet()) enumeratedInhibitors |= mixedTerm.getKey() | mixedTerm.getValue();
		
		enumeratedInhibitors &= ~b.nonInhibitors;
		
		return enumeratedInhibitors;
	}
	
	public static final int enumeratedActivators(BeforeStates b) {
		int enumeratedActivators = 0;

		for (int gterm : b.beforeON()) enumeratedActivators |= gterm;
		for (int rterm : b.requiredBeforeOFF()) enumeratedActivators |= rterm;
		for (Entry<Integer,Integer> mixedTerm : b.beforeOFF().entrySet()) enumeratedActivators |= mixedTerm.getKey() | mixedTerm.getValue();
		
		enumeratedActivators &= ~b.nonActivators;
		
		return enumeratedActivators;
	}
	
	public static final Predicate<Integer> satisfyingActivators(final BeforeStates b) {
		return new Predicate<Integer>() {
			@Override public boolean apply(Integer input) {
				for (int gterm : b.beforeON()) if ((gterm & input) == 0) return false;
				return true;
			}
		};
	}
	
	public static final Predicate<EnumSolution> satisfyingMixed(final BeforeStates b) {
		return new Predicate<EnumSolution>() {
			@Override public boolean apply(EnumSolution input) {
				for (int rterm : b.requiredBeforeOFF())
					if ((rterm & input.inhibitors) == 0) return false;
				for (Entry<Integer,Integer> mixed : b.beforeOFF().entrySet()) 
					if ((mixed.getKey() & input.inhibitors) == 0 && (mixed.getValue() & input.activators) != 0) return false;
				return true;
			}
		};
	}
	
	public static final Iterable<EnumSolution> enumerateSolutions(
			int enumeratedActivators,
			int enumeratedInhibitors,
			Predicate<Integer> satisfyingActivators,
			Predicate<EnumSolution> satisfyingMixed) {
		
		int all = enumeratedActivators | enumeratedInhibitors;
		int overlap = enumeratedActivators & enumeratedInhibitors;
		int justActivators = enumeratedActivators & ~overlap;
		
		final ImmutableSet.Builder<Integer> activatingComponents = ImmutableSet.builder();
		final ImmutableSet.Builder<Integer> inhibitingComponents = ImmutableSet.builder();
		final ImmutableSet.Builder<Integer> mixedComponents = ImmutableSet.builder();

		for (int vertex : Dynamic.bitmask) {
			if ((all & vertex)==0) {
				//no-op
			} else {
				(	(overlap & vertex)!=0 			? mixedComponents :
					(justActivators & vertex)!=0 	? activatingComponents :
					inhibitingComponents
				).add(vertex);
			}
		}

		Set<Integer> ac = activatingComponents.build();
		Set<Integer> ic = inhibitingComponents.build();
		Set<Integer> mc = mixedComponents.build();
		
		Iterable<EnumSolution> prefilter;
		
		if (ac.isEmpty() && ic.isEmpty()) {
			prefilter = mc.isEmpty() ? justNeither : new MixedIterable(mc);
		} else if (!(ac.isEmpty() || ic.isEmpty())) {
			final Iterable<Integer> powerActivators = 
				Iterables.filter(
					Iterables.transform( Sets.powerSet(ac), merge )
					, satisfyingActivators
				);
			
			final Iterable<Integer> powerInhibitors =
				Iterables.transform( Sets.powerSet(ic), merge );
					
			prefilter = mc.isEmpty() ?
				new JustBothIterable(powerActivators, powerInhibitors) :
				new JointEnumIterable(new JustBothIterable(powerActivators, powerInhibitors), new MixedIterable(mc));
		} else {
			final Iterable<Integer> source = ac.isEmpty() ? 
				Iterables.transform( Sets.powerSet(ic), merge ) :
				Iterables.filter(
					Iterables.transform( Sets.powerSet(ac), merge )
					, satisfyingActivators
				);
			prefilter = mc.isEmpty() ?
				new JustOneIterable(source, ac.isEmpty()) :
				new JointEnumIterable(new JustOneIterable(source, ac.isEmpty()), new MixedIterable(mc));
		}
		
		return Iterables.filter(prefilter, satisfyingMixed);
	}
	
	public static class EnumSolution implements Comparable<EnumSolution> {
		public final int inhibitors;
		public final int activators;
		
		EnumSolution(final int inhibitors, final int activators) {
			this.inhibitors = inhibitors;
			this.activators = activators;
		}

		public EnumSolution join(EnumSolution other) {
			return new EnumSolution(inhibitors | other.inhibitors, activators | other.activators);
		}
		
		public static EnumSolution make(final int inhibitors, final int activators) {
			if ((inhibitors & activators)!=0) throw new IllegalArgumentException();
			return new EnumSolution(inhibitors, activators);
		}

		@Override public int compareTo(EnumSolution arg0) {
			if (this == arg0) return 0;
				
			return Integer.bitCount(inhibitors | activators) - Integer.bitCount(arg0.inhibitors | arg0.activators);
		}
				
	}
	
	private static final class JointEnumIterable implements Iterable<EnumSolution> {
		private final Iterable<EnumSolution> expensive;
		private final Iterable<EnumSolution> cheap;
		
		JointEnumIterable(Iterable<EnumSolution> expensive, Iterable<EnumSolution> cheap) {
			this.expensive = expensive;
			this.cheap = cheap;
		}

		@Override public Iterator<EnumSolution> iterator() { return new Iterator<EnumSolution>(){

			private final Iterator<EnumSolution> exp = expensive.iterator();
			private EnumSolution current = exp.next();
			private Iterator<EnumSolution> chp = cheap.iterator();
			
			@Override public boolean hasNext() {
				return exp.hasNext() || chp.hasNext();
			}

			@Override public EnumSolution next() {
				if (chp.hasNext()) {
					return current.join(chp.next());
				} else if (exp.hasNext()) {
					current = exp.next();
					chp = cheap.iterator();
					return next();
				} else throw new NoSuchElementException();
			}

			@Override public void remove() { throw new UnsupportedOperationException(); }
				
		}; }
		
		
	}
	
	private static final class MixedIterable implements Iterable<EnumSolution> {

		private final Set<Integer> mixed;
		private final Set<Set<Integer>> powerSet;
		MixedIterable(Set<Integer> on) {
			mixed = on;
			powerSet = Sets.powerSet(mixed);
		}
		
		@Override public Iterator<EnumSolution> iterator() { return new Iterator<EnumSolution>(){

			private final Iterator<Set<Integer>> top = powerSet.iterator();
			private Set<Integer> current = top.next();
			private int inhibitors = merge(current);
			private Iterator<Set<Integer>> bottom = Sets.powerSet(Sets.difference(mixed, current)).iterator();
			
			@Override
			public boolean hasNext() {
				return top.hasNext() || bottom.hasNext();
			}

			@Override
			public EnumSolution next() {
				if (bottom.hasNext()) {
					return new EnumSolution(inhibitors, merge(bottom.next()));
				} else if (top.hasNext()) {
					current = top.next();
					inhibitors = merge(current);
					bottom = Sets.powerSet(Sets.difference(mixed, current)).iterator();
					return next();
				} else throw new NoSuchElementException();
			}

			@Override public void remove() { throw new UnsupportedOperationException(); }
		
		}; }
		
	}
	
	private static final class JustBothIterable implements Iterable<EnumSolution> {
		private final Iterable<Integer> inhibitors;
		private final Iterable<Integer> activators;
		
		JustBothIterable(final Iterable<Integer> justInhibitors, final Iterable<Integer> justActivators) {
			inhibitors = justInhibitors;
			activators = justActivators;
		}

		@Override
		public Iterator<EnumSolution> iterator() { return new Iterator<EnumSolution>(){

			private final Iterator<Integer> top = activators.iterator();
			private int activator = top.next();
			private Iterator<Integer> bottom = inhibitors.iterator();
			
			@Override
			public boolean hasNext() {
				return top.hasNext() || bottom.hasNext();
			}

			@Override
			public EnumSolution next() {
				if (bottom.hasNext()) {
					return new EnumSolution(bottom.next(),activator);
				} else if (top.hasNext()) {
					activator = top.next();
					bottom = inhibitors.iterator();
					return next();
				} else throw new NoSuchElementException();
			}

			@Override public void remove() { throw new UnsupportedOperationException(); }
				
		}; }
	}
	
	private static final class JustOneIterable implements Iterable<EnumSolution> {
		private final boolean isInhibiting;
		private final Iterable<Integer> source;
		JustOneIterable(final Iterable<Integer> justSource, final boolean isInhibiting) {
			source = justSource;
			this.isInhibiting = isInhibiting;
		}
		
		@Override public Iterator<EnumSolution> iterator() {
			return Iterators.transform(source.iterator(), isInhibiting ? inhibiting : activating);
		}
		
		private static final Function<Integer,EnumSolution> inhibiting = new Function<Integer,EnumSolution>() {
			@Override public EnumSolution apply(Integer from) {
				return new EnumSolution(from,0);
			}			
		};
		
		private static final Function<Integer,EnumSolution> activating = new Function<Integer,EnumSolution>() {
			@Override public EnumSolution apply(Integer from) {
				return new EnumSolution(0,from);
			}
		};
	}
	
	private static final Iterable<EnumSolution> justNeither = Collections.singleton(new EnumSolution(0,0));
	
	
	
	private static final int merge(Iterable<Integer> source) {
		int merge = 0;
		for (int term : source) merge |= term;
		return merge;
	}
	
	private static final Function<Iterable<Integer>,Integer> merge = new Function<Iterable<Integer>,Integer>() {
		@Override public Integer apply(Iterable<Integer> from) { return merge(from); }
	};
	
}
